<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    // --------------------从Template --> 模板AST-------------------------
    const State = {
        initial: 1,
        tagOpen: 2,
        tagName: 3,
        text: 4,
        tagEnd: 5,
        tagEndName: 6
    }

    function isAlpha(char) {
        return char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z'
    }

    function tokenize(str) {
        let currentState = State.initial
        const chars = [] // 字符缓冲区
        const tokens = [] // tokens
        while (str) {
            const char = str[0] // 取出，没有消费
            switch (currentState) {
                case State.initial:
                    if (char === '<') {
                        currentState = State.tagOpen
                        str = str.slice(1) // 消费
                    } else if (isAlpha(char)) {
                        currentState = State.text
                        chars.push(char) // 推入缓冲区
                        str = str.slice(1) // 消费
                    }
                    break
                case State.tagOpen:
                    if (isAlpha(char)) {
                        currentState = State.tagName
                        chars.push(char)
                        str = str.slice(1)
                    } else if (char === '/') {
                        currentState = State.tagEnd
                        str = str.slice(1)
                    }
                    break
                case State.tagName:
                    if (isAlpha(char)) {
                        chars.push(char)
                        str = str.slice(1)
                    } else if (char === '>') {
                        currentState = State.initial
                        tokens.push({ // 推入token
                            type: 'tag',
                            name: chars.join('')
                        })
                        chars.length = 0 // 清空缓冲区
                        str = str.slice(1)
                    }
                    break
                case State.text:
                    if (isAlpha(char)) {
                        chars.push(char)
                        str = str.slice(1)
                    } else if (char === '<') {
                        currentState = State.tagOpen
                        tokens.push({
                            type: 'text',
                            content: chars.join('')
                        })
                        chars.length = 0
                        str = str.slice(1)
                    }
                    break
                case State.tagEnd:
                    if (isAlpha(char)) {
                        currentState = State.tagEndName
                        chars.push(char)
                        str = str.slice(1)
                    }
                    break
                case State.tagEndName:
                    if (isAlpha(char)) {
                        chars.push(char)
                        str = str.slice(1)
                    } else if (char === '>') {
                        currentState = State.initial
                        tokens.push({
                            type: 'tagEnd',
                            name: chars.join('')
                        })
                        chars.length = 0
                        str = str.slice(1)
                    }
                    break
            }
        }
        return tokens
    }

    function parse(str) {
        const tokens = tokenize(str)

        const root = {
            type: 'Root',
            children: []
        }
        const elementStack = [root] // 一开始只有root

        while (tokens.length) {
            const parent = elementStack[elementStack.length - 1] // 栈顶
            const t = tokens[0] // 每次都取出第一个token
            switch (t.type) {
                case 'tag':
                    const elementNode = {
                        type: 'Element',
                        tag: t.name,
                        children: []
                    }
                    parent.children.push(elementNode) // 父元素的children收集该子节点
                    elementStack.push(elementNode) // 栈顶入栈
                    break
                case 'text':
                    const textNode = {
                        type: 'Text',
                        content: t.content
                    }
                    parent.children.push(textNode) // 父元素的children收集该子节点
                    break
                case 'tagEnd':
                    elementStack.pop() // 栈顶出栈
                    break
            }
            tokens.shift() // 消费token
        }

        return root
    }

    // -------------------从模板AST --> jsAST
    // 打印ast节点信息
    function dump(node, indent = 0) {
        const type = node.type // 节点类型
        // 根据节点类型来输出信息
        // root -> '' , element -> node.tag , Text -> node.content
        const desc = type === 'Root' ? '' : type === 'Element' ? node.tag : node.content
        console.log(`${'-'.repeat(indent)}${type}:${desc}`)
        // 递归打印子节点
        if (node.children) {
            node.children.forEach(n => dump(n, indent + 2))
        }
    }

    // 对模板AST中节点进行访问
    function traverseNode(ast, context) {
        context.currentNode = ast // 将上下文中的currentNode设置为当前node
        const exitFns = [] // 退出阶段执行的回调函数
        const transforms = context.transform // 转变数组transforms
        if (transforms) {
            for (let i = 0; i < transforms.length; i++) {
                const exitFn = transforms[i](context.currentNode, context) // 转换逻辑执行后返回一个该节点退出阶段才执行的回调函数
                if (exitFn) { // 如果存在的话，就推进退出阶段执行的回调函数数组中，在子节点全部处理完之后遍历执行
                    exitFns.push(exitFn)
                }
                // 每次transform都有可能导致currentNode变为null, 如果为null直接返回
                if (!context.currentNode) return
            }

        }
        // 深度优先遍历
        const children = context.currentNode.children
        if (children) {
            for (let i = 0; i < children.length; i++) {
                // 对children进行深度遍历之前，设置上下文
                context.parent = context.currentNode
                context.childIndex = i
                traverseNode(children[i], context)
            }
        }

        // 子节点已经全部处理完毕，退出时要执行回调函数
        // 逆序执行
        let len = exitFns.length
        while (len--) {
            exitFns[len]()
        }
    }

    function traverse(ast) {
        // 上下文对象
        const context = {
            currentNode: null,
            parent: null,
            childIndex: 0, // 子元素在父元素children中的索引
            replaceNode(node) {
                // 替换节点，修改ast
                context.parent.children[context.childIndex] = node
                // 当前节点已经被传进来的 node 替代，所以将上下文的currentNode换成node
                context.currentNode = node
            },
            removeNode() {
                if (context.parent) {
                    context.parent.children.splice(context.childIndex, 1)
                    context.currentNode = null
                }
            },
            transform: [
                transformText,
                transformElement,
                transformRoot
            ]
        }
        console.log('前面')
        dump(ast)
        traverseNode(ast, context)
        console.log('后面')
        dump(ast)
    }

    // -------------------转换辅助函数---------------------
    // 转换文本节点
    function transformText(node, context) {
        if (node.tag !== 'Text') return // 如果不是Text，直接return
        // 文本节点对应的JS AST是一个StringLiteral
        node.jsNode = createStringLiteral(node.content)
    }

    // 转换Element节点
    function transformElement(node, context) {
        if (node.type !== 'Element') return // 不是Element直接返回
        return () => { // 返回函数保证子节点都完成之后才处理
            const callExp = createCallExpression('h', [createStringLiteral(node.tag)]) // arguments数组第一个参数是node.tag
            node.children.length === 1 // children个数
                ? callExp.arguments.push(createStringLiteral(node.children[0].jsNode))
                : callExp.arguments.push(createArrayExpression(node.children.map(c => c.jsNode)))
            node.jsNode = callExp // 设置当前标签的jsNode
        }
    }

    // 转换根节点
    function transformRoot(node) {
        if (node.type !== 'Root') return
        return () => { // 回调函数，等子节点全部搞定
            const vnodeJSAST = node.children[0].jsNode
            node.jsNode = {
                type: 'FunctionDecl',
                id: {type: 'Identifier', name: 'render'},
                params: [],
                body: [
                    {
                        type: 'ReturnStatement',
                        return: vnodeJSAST
                    }
                ]
            }

        }
    }

    // 创建StringLiteral节点
    function createStringLiteral(value) {
        return {
            type: 'StringLiteral',
            value
        }
    }

    // 创建标识符
    function createIdentifier(name) {
        return {
            type: 'Identifier',
            name
        }
    }

    // 创建ArrayExpression节点
    function createArrayExpression(elements) {
        return {
            type: 'ArrayExpression',
            elements
        }
    }

    // 创建CallExpression节点
    function createCallExpression(callee, arguments) {
        return {
            type: 'CallExpression',
            callee: createIdentifier(callee),
            arguments
        }
    }

    // ----------------------从jsAST到Render函数----------------------
    function compile(template) {
        const ast = parse(template) // 模板到模板AST
        traverse(ast) // 模板AST到jsAST
        const code = generate(ast.jsNode) // jsAST到render code
        return code
    }

    function generate(node) {
        const context = {
            code: '',
            push(code) {
                context.code += code
            },
            currentIndent: 0, // 缩进个数
            newLine() { // 换行
                context.code += '\n' + ' '.repeat(context.currentIndent * 2)
            },
            indent() { // 缩进
                context.currentIndent++
                context.newLine()
            },
            deIndent() { // 取消缩进
                context.currentIndent--
                context.newLine()
            }
        }
        genNode(node, context)
        return context.code
    }

    function genNode(node, context) {
        switch (node.type) {
            case 'FunctionDecl':
                genFunctionDecl(node, context)
                break
            case 'ReturnStatement':
                genReturnStatement(node, context)
                break
            case 'CallExpression':
                genCallExpression(node, context)
                break
            case 'ArrayExpression':
                genArrayExpression(node, context)
                break
            case 'StringLiteral':
                genStringLiteral(node, context)
                break
        }
    }

    function genFunctionDecl(node, context) {
        const {push, indent, deIndent} = context
        push(`function ${node.id.name} (`)
        genNodeList(node.params, context)
        push(`) {`)
        indent() // 缩进
        node.body.forEach(n => genNode(n, context)) // body是一个数组，每个值都是一条语句
        deIndent() // 取消缩进
        push('}')
    }

    function genNodeList(nodes, context) {
        const {push} = context
        for (let i = 0; i< nodes.length; i++){
            const node = nodes[i]
            genNode(node, context)
            if (i < nodes.length - 1) {
                push(', ')
            }
        }
    }

    function genArrayExpression(node, context) {
        const {push} = context
        push('[')
        genNodeList(node.elements, context)
        push(']')

    }

    function genReturnStatement(node, context) {
        const {push} = context
        push('return ')
        genNode(node.return, context)
    }

    function genStringLiteral(node, context) {
        const {push} = context
        push(`'${node.value}'`)
    }

    function genCallExpression(node, context) {
        const {push} = context
        const {callee, arguments: args} = node // 取得引用函数的名称callee和参数
        push(`${callee.name}(`)
        genNodeList(args, context) // 生成参数列表
        push(')')
    }

    const template = `<div><p>Vue</p><p>Template</p></div>`
    // const ast = parse(template)
    // traverse(ast)
    const render =compile(template)
    console.log(render)
</script>
</body>
</html>
